package com.polaris.live.manager

import android.app.Activity
import com.blankj.utilcode.util.AppUtils
import com.blankj.utilcode.util.NetworkUtils
import com.blankj.utilcode.util.Utils
import com.polaris.live.common.AppModule
import com.polaris.live.common.cacha.CacheUtil
import com.polaris.live.common.cacha.UserManager
import com.polaris.live.common.constant.AppConst
import com.polaris.live.common.manager.CoroutineScopeManager
import com.polaris.live.common.network.getSuccessResult
import com.polaris.live.common.util.JsonUtils
import com.polaris.live.common.util.LogExt
import com.polaris.live.constant.TrackEventConst
import com.polaris.live.im.db.database.TrackDatabase
import com.polaris.live.im.db.entity.TrackPushEntity
import com.polaris.live.net.pusherService
import com.polaris.live.resp.go_resp.EventTrackingDataBean
import com.polaris.live.utils.CommonUtils
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors

/**
 * 埋点上报管理器
 *
 * @author Created by lucas on 2024/1/1 07:10
 */
object TrackPushManager {

    const val TAG = "TrackPushManager"

    //上传线程
    private val singleThreadDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()

    private val events = ConcurrentHashMap<String, TrackPushEntity>()

    init {
        //监听前后台状态
        AppUtils.registerAppStatusChangedListener(object : Utils.OnAppStatusChangedListener {
            override fun onForeground(activity: Activity?) {
                triggerPush()
            }

            override fun onBackground(activity: Activity?) {
                triggerPush()
            }
        })

        //监听网络状态
        NetworkUtils.registerNetworkStatusChangedListener(object :
            NetworkUtils.OnNetworkStatusChangedListener {
            override fun onConnected(networkType: NetworkUtils.NetworkType?) {
                triggerPush()
            }

            override fun onDisconnected() {
            }
        })
    }

    /**
     * 发布埋点
     *
     * @param page 页面
     * @param event 事件
     * @param params 参数
     */
    fun postEvent(
        page: String,
        event: String,
        pageId: String,
        params: Map<String, String> = emptyMap(),
        eventType: String = TrackEventConst.TRACK_CLICK,
    ) {
        try {
            invokePostEvent(page, event, eventType, pageId, params)
        } catch (t: Throwable) {
            LogExt.logE(TAG, "postEvent error", t)
        }
    }

    private fun invokePostEvent(
        pageName: String,
        event: String,
        eventType: String,
        pageId: String,
        params: Map<String, String> = emptyMap(),
    ) {
        val eventId = CommonUtils.uuid()

        val userId = if (UserManager.isLogin()) {
            UserManager.getUserId()
        } else {
            null
        }
        val uniqueId = AppModule.getUniqueDeviceId()
        val appVersion = AppModule.getVersionName()
        val buildVersion = AppModule.getAppVersion()
        val ext = if (params.isEmpty()) {
            null
        } else {
            JsonUtils.toJson(params)
        }

        val entity = TrackPushEntity(
            eventId,
            uniqueId,
            userId,
            appVersion,
            buildVersion,
            null,
            pageName,
            pageId,
            event,
            eventType,
            ext,
            CacheUtil.getServerTime()
        ).apply {
            events[eventId] = this
        }

        saveEvent(entity)
    }

    private fun saveEvent(entity: TrackPushEntity) {
        GlobalScope.launch(CoroutineScopeManager.defaultDispatcher) {
            invokeSaveEvent(entity)

            triggerSaveDb()
        }

        triggerPush()
    }

    private suspend fun invokeSaveEvent(entity: TrackPushEntity) {
        TrackDatabase.instance.trackPushEntityDao().insert(entity)

        events.remove(entity.eventId)
    }

    private suspend fun triggerSaveDb() {
        if (events.isEmpty()) {
            return
        }

        events.forEach { (_, e) ->
            invokeSaveEvent(e)
        }
    }

    @Volatile
    private var pushing: Boolean = false

    private fun triggerPush() {
        if (pushing) {
            return
        }

        pushing = true

        GlobalScope.launch(singleThreadDispatcher) {
            try {
                invokeTriggerPush()
            } catch (t: Throwable) {
                LogExt.logE(TAG, "sendEvent error", t)

                //如果发生错误延迟一秒后重试
                delay(1000)

                triggerPush()
            } finally {
                pushing = false
            }
        }
    }

    private suspend fun invokeTriggerPush() {
        val limit = 20
        var hasMore = true
        while (hasMore) {
            val list = TrackDatabase.instance.trackPushEntityDao().findTraces(limit)
            if (list.isEmpty()) {
                return
            }

            val resp = pusherService.pushTrack(
                EventTrackingDataBean(
                    AppConst.APP_TYPE,
                    AppConst.PLATFORM_TYPE,
                    AppConst.VARIANT_TYPE,
                    list.map {
                        EventTrackingDataBean.EventTrackingData(
                            it.eventId,
                            it.uniqueId ?: AppModule.requireUniqueDeviceId(),
                            it.userId,
                            it.appVersion,
                            it.buildVersion,
                            it.entrancePage,
                            it.page,
                            it.pageUuid,
                            it.event,
                            it.eventType,
                            it.ext,
                            it.eventTimestamp
                        )
                    }
                )
            )
            val apiResp = resp.getSuccessResult()
                ?: throw IllegalStateException("pushTrack error ${resp.code} ${resp.errorMessage}")

            TrackDatabase.instance.trackPushEntityDao().deleteByEventIds(apiResp.msgIds)

            LogExt.logI(TAG) {
                "pushTrack ${
                    JsonUtils.toPrettyJson(list.filter {
                        apiResp.msgIds.contains(it.eventId)
                    })
                }"
            }

            hasMore = list.size >= limit
        }
    }
}